package jmine.tec.di.resolver.impl;

import java.util.List;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.di.DependencyInjectionMessages;
import jmine.tec.di.resolver.ObjectResolver;
import jmine.tec.di.scope.TypedScope;
import jmine.tec.di.type.TypeConverter;
import jmine.tec.di.type.TypeConvertionException;
import jmine.tec.di.wrapper.Wrapper;
import jmine.tec.utils.Tuple;

/**
 * Resolve objetos dinamicamente do {@link TypedScope}
 */
@Unmodifiable
public class DynamicObjectResolver implements ObjectResolver {

    private final Class<?> type;

    private final String name;

    private final TypeConverter<?> typeConverter;

    private final String defaultValue;

    /**
     * C'tor
     * 
     * @param type the type
     * @param name the name
     * @param typeConverter the converter
     * @param defaultValue the default valule
     */
    public DynamicObjectResolver(Class<?> type, String name, TypeConverter<?> typeConverter, String defaultValue) {
        super();
        this.type = type;
        this.name = name;
        this.typeConverter = typeConverter;
        this.defaultValue = defaultValue;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.di.cache.TypeMatch#isTypeCompatible(java.lang.Class)
     */
    public boolean isTypeCompatible(Class<?> requiredType) {
        return requiredType.isAssignableFrom(this.type);
    }

    /**
     * {@inheritDoc}
     */
    public Object resolveObject(TypedScope properties, Wrapper wrapper, Class<?> requiredComponentClass) throws TypeConvertionException {
        final Object resolved = this.getObject(properties);
        if (resolved == null) {
            return null;
        }
        if (wrapper.isManyWrapper() && wrapper.isTypeCompatible(resolved.getClass())) {
            if (wrapper.checkContainedType(resolved, requiredComponentClass)) {
                return resolved;
            } else {
                return this.collectConvertWrap(resolved, requiredComponentClass, wrapper);
            }
        }
        try {
            final Object converted =
                    requiredComponentClass.isInstance(resolved) ? resolved : this.typeConverter.convert(String.valueOf(resolved),
                            requiredComponentClass);
            return wrapper.wrap(this.name, converted, this.type);
        } catch (NumberFormatException e) {
            throw new TypeConvertionException(requiredComponentClass, resolved,
                    DependencyInjectionMessages.FORMATO_INCORRETO.create(this.name));
        } catch (IllegalArgumentException e) {
            throw new TypeConvertionException(requiredComponentClass, resolved,
                    DependencyInjectionMessages.FORMATO_INCORRETO.create(this.name));
        }
    }

    /**
     * Devolve o objeto ou o 'defaultValue' se este estiver cadastrado
     * 
     * @param scope {@link TypedScope}
     * @return {@link Object}
     */
    private Object getObject(TypedScope scope) {
        Object fromScope = scope.get(this.name, Object.class);
        if (fromScope == null && this.defaultValue != null) {
            if (this.defaultValue.startsWith("$")) {
                return scope.get(this.defaultValue, Object.class);
            } else {
                return this.defaultValue;
            }
        }
        return fromScope;
    }

    /**
     * Encontramos um objeto que pode ser 'wrapped', mas os tipos dos conteudos nao batem. Vamos extrair o conteudo da colecao, converter e
     * fazer o wrap novamente
     * 
     * @param resolved Object
     * @param requiredComponentClass Class
     * @param wrapper Wrapper
     * @return Object
     * @throws TypeConvertionException e
     */
    private Object collectConvertWrap(Object resolved, Class<?> requiredComponentClass, Wrapper wrapper) throws TypeConvertionException {
        List<Tuple<?, ?>> collected = wrapper.collect(resolved, this.name);
        String[] names = new String[collected.size()];
        Object[] values = new Object[collected.size()];
        for (int i = 0; i < collected.size(); i++) {
            final Tuple<?, ?> tuple = collected.get(i);
            names[i] = tuple.getHead().toString();
            values[i] = this.typeConverter.convert(String.valueOf(tuple.tail()), requiredComponentClass);
        }
        return wrapper.wrap(names, values, requiredComponentClass);
    }

    /**
     * @param properties the props
     * @return boolean
     */
    public boolean isResolvable(TypedScope properties) {
        return this.defaultValue != null || (properties.containsKey(this.name) && properties.get(this.name, Object.class) != null);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isStatic() {
        return false;
    }

    /**
     * @return the name
     */
    public String getName() {
        return this.name;
    }

}
